#include "dev/console.h"
#include "tools/klib.h"
#include "comm/cpu_instr.h"
#include "dev/tty.h"
#include "cpu/irq.h"

#define CONSOLE_NUM 8
static console_t console_buff[CONSOLE_NUM];
static int curr_console_idx = 0;

// 函数声明
static void scroll_up(console_t * console, int lines);
static int read_cursor_pos(void);
static int update_cursor_pos(console_t * console);
static int cursor_backward(console_t * console, int n);
static void erase_rows(console_t * console, int start_lines, int end_lines);
static void cursor_forward(console_t * console, int n);
static void show_char(console_t * console, char c);
static void erase_backward(console_t * console);
static void cursor_to_col0(console_t * console);
static void cursor_to_next_row(console_t * console);
static void console_clear_display(console_t * console);

// 命令行上滚
static void scroll_up(console_t * console, int lines)
{
    disp_char_t * src = console->disp_base + lines*console->display_col;
    disp_char_t * dest = console->disp_base;
    uint32_t size = (console->display_row - lines) * console->display_col * sizeof(disp_char_t);
    kernel_memcpy(dest, src, size);
    // 清空上滚后 留下的部分
    erase_rows(console, console->display_row-lines, console->display_row-1);
    console->cursor_row--;
}

static int read_cursor_pos(void)
{
    int pos;
    irq_status_t status = irq_enter_protection();
    // 读取低8位
    outb(0x3d4, 0xf);
    pos = inb(0x3d5);
    // 读取高8位
    outb(0x3d4, 0xe);
    pos |= inb(0x3d5) << 8;
    irq_quit_protection(status);
    return pos;
}

// 修改光标在显示屏中的位置
static int update_cursor_pos(console_t * console)
{
    uint16_t pos =  (console - console_buff) * console->display_row * console->display_col;
    pos += console->cursor_row * console->display_col + console->cursor_col;

    irq_status_t status = irq_enter_protection();
    // 低8位
    outb(0x3d4, 0xf);
    outb(0x3d5, (uint8_t)(pos & 0xff));
    // 高8位
    outb(0x3d4, 0xe);
    outb(0x3d5, (uint8_t)(pos>>8 & 0xff)); 
    irq_quit_protection(status);
    return pos;
}

static int cursor_backward(console_t * console, int n)
{
    int status = -1;
    for(int i=0;i<n;i++)
    {
        if(console->cursor_col > 0)
        {
            console->cursor_col--;
            status = 0;
        }
        else if(console->cursor_row > 0)
        {
            console->cursor_row--;
            console->cursor_col = console->display_col - 1;
            status = 0;
        }
    }
    return status;
}

static void erase_rows(console_t * console, int start_lines, int end_lines)
{
    disp_char_t * start = console->disp_base + start_lines * console->display_col;
    disp_char_t * end = console->disp_base + (end_lines+1) * console->display_col;
    while(start < end)
    {
        start->v = ' ';
        start->background = console->background;
        start->foreground = console->foreground;
        start++;
    }
}


static void cursor_forward(console_t * console, int n)
{
    for(int i=0;i<n;i++)
    {
        if(++console->cursor_col >= console->display_col)
        {
            console->cursor_col = 0;
            console->cursor_row++;
            if(console->cursor_row >= console->display_row)
            {
                scroll_up(console,1);
            }
        }
    }
}

static void show_char(console_t * console, char c)
{
    // 光标在屏幕中的偏移
    int offset = console->cursor_col + console->display_col * console->cursor_row;
    disp_char_t * p =  console->disp_base + offset;
    p->c = c;
    p->foreground = console->foreground;
    p->background = console->background;
    cursor_forward(console, 1);
}

static void erase_backward(console_t * console)
{
    if(cursor_backward(console, 1) == 0)
    {
        show_char(console, ' ');
        cursor_backward(console, 1);
    }
}

static void cursor_to_col0(console_t * console)
{
    console->cursor_col = 0;
}

static void cursor_to_next_row(console_t * console)
{
    console->cursor_row++;
    if(console->cursor_row >= console->display_row)
    {
        scroll_up(console, 1);
    }
}

static void console_clear_display(console_t * console)
{
    int size = console->display_col * console->display_row;
    disp_char_t *pos = console->disp_base;
    for(int i=0;i<size;i++, pos++)
    {
        pos->c = ' ';
        pos->background = console->background;
        pos->foreground = console->background;
    }
}

static void write_normal(console_t * console, char ch)
{
    if(ch == '\n')
    {
        cursor_to_next_row(console);
    }
    else if(ch == 0x7f)
    {
        erase_backward(console);
    }
    else if(ch == '\b')
    {
        cursor_backward(console, 1);
    } 
    else if(ch == '\r')
    {
        cursor_to_col0(console);
    }
    else if(ch == ANSI_ESC)
    {
        console->write_status = CONSOLE_WRITE_ESC;
    }
    else
    {
        if(ch>=' ' && ch <= '~')
        {
            show_char(console, ch);
        }
    }
}

static void save_cursor(console_t * console)
{
    console->old_cursor_row = console->cursor_row;
    console->old_cursor_col = console->cursor_col;
}

static void restore_cursor(console_t *console)
{
    console->cursor_row = console->old_cursor_row;
    console->cursor_col = console->old_cursor_col;
}

static void clear_esc_param(console_t * console)
{
    kernel_memset(console->esc_param, 0, ESC_PARAM_MAX);
    console->esc_param_index = 0;
}

static void set_font_style(console_t * console)
{
    static const color_t color_table[] = {
        COLOR_Black, COLOR_Red, COLOR_Green, COLOR_Yellow, 
        COLOR_Blue, COLOR_Magenta,COLOR_Cyan,COLOR_White,
    };
    for(int i=0; i<=console->esc_param_index; i++)
    {
        int param = console->esc_param[i];
        if(param >= 30 && param <= 37)
        {
            console->foreground = color_table[param - 30];
        }
        else if(param >= 40 && param <= 47)
        {
            console->background = color_table[param - 40];
        }
        else if(param == 39)
        {
            console->foreground = COLOR_White;
        }
        else if(param == 49)
        {
            console->background = COLOR_Black;
        }
    }
    
}

static void write_esc(console_t * console, char ch)
{
    if(ch == '7')
    {
        save_cursor(console);
        console->write_status = CONSOLE_WRITE_NORMAL;
    }
    else if(ch == '8')
    {
        restore_cursor(console);
        console->write_status = CONSOLE_WRITE_NORMAL;
    }  
    else if(ch == '[')
    {
        clear_esc_param(console);
        console->write_status = CONSOLE_WRITE_SQUARE;
    }
    else
    {
        console->write_status = CONSOLE_WRITE_NORMAL;
    }
}

static void erase_in_diaplays(console_t * console)
{
    // 
    if(console->esc_param_index < 0)
    {
        return ;
    }
    int param = console->esc_param[console->esc_param_index];
    if(param == 2)
    {
        erase_rows(console, 0, console->display_row-1);
        console->cursor_col = console->cursor_row = 0;
    }
}

static void cursor_goto(console_t * console)
{
    console->cursor_row = console->esc_param[0];
    console->cursor_col = console->esc_param[1];
}

static void cursor_left(console_t * console, int n)
{
    if(n==0)
    {
        n = 1;
    }

    int col = console->cursor_col - n;
    console->cursor_col = (col >= 0) ? col : 0;
}

static void cursor_right(console_t * console, int n)
{
    if(n==0)
    {
        n = 1;
    }

    int col = console->cursor_col + n;
    console->cursor_col = (col < console->display_col) ? col : console->display_col-1;
}

static void write_square(console_t * console, char ch)
{
    if(ch >= '0' && ch <= '9')
    {
        int *param = console->esc_param + console->esc_param_index;
        *param = *param * 10 + ch - '0';
    }
    else if(ch == ';' && console->esc_param_index < ESC_PARAM_MAX)
    {
        console->esc_param_index++;
    }
    else 
    {
        if(ch == 'm')
        {
            set_font_style(console);
        } 
        else if(ch == 'D')
        {
            cursor_left(console, console->esc_param[0]);
        }
        else if(ch == 'C')
        {
            cursor_right(console, console->esc_param[0]);
        }
        else if(ch == 'H' || ch == 'f') 
        {
            cursor_goto(console);
        }
        else if(ch == 'J')
        {
            erase_in_diaplays(console);
        }
        console->write_status = CONSOLE_WRITE_NORMAL;
    }
}

int console_init(int console_idx)
{
    console_t * console = console_buff + console_idx;
    console->display_col = CONSOLE_COL_MAX;
    console->display_row = CONSOLE_ROW_MAX;
    console->disp_base = (disp_char_t *)(CONSOLE_DISP_ADDR + console_idx * CONSOLE_COL_MAX * CONSOLE_ROW_MAX * 2);
    console->foreground = COLOR_White;
    console->background = COLOR_Black;
    if(console_idx == 0)
    {
        int cursor_pos = read_cursor_pos();
        console->cursor_row = cursor_pos / console->display_col;
        console->cursor_col = cursor_pos % console->display_col;
    }
    else
    {
        console->cursor_row = console->cursor_col = 0;
        console_clear_display(console);
    } 
    console->old_cursor_col = console->cursor_col;
    console->old_cursor_row = console->cursor_row;
    console->write_status = CONSOLE_WRITE_NORMAL;
    mutex_init(&console->mutex);
    return 0;
}

int console_write(tty_t * tty)
{   
    console_t * console = console_buff + tty->console_idx;
    int len = 0;
    mutex_lock(&console->mutex);
    char ch;
    while (tty_fifo_get(&tty->outfifo, &ch) >= 0)
    {
        sema_notify(&tty->out_semaphore);
        if(console->write_status == CONSOLE_WRITE_NORMAL)
        {
            write_normal(console, ch);
        }
        else if(console->write_status == CONSOLE_WRITE_ESC)
        {
            write_esc(console, ch);
        }
        else if(console->write_status == CONSOLE_WRITE_SQUARE)
        {
            write_square(console, ch);
        }   
        len++;
    }
    mutex_unlock(&console->mutex);
    if(tty->console_idx == curr_console_idx)
    {
        update_cursor_pos(console);
    }

    return len;
}

void console_close(int console)
{
    return;
}

void console_select(int tty_idx)
{
    console_t * console = console_buff + tty_idx;
    if(console->disp_base == 0)
    {
        console_init(tty_idx);
    }

    // 修改光标所在显示屏
    uint16_t pos = tty_idx * console->display_col * console->display_row;  
    // 低8位
    outb(0x3d4, 0xD);
    outb(0x3d5, (uint8_t)(pos & 0xff));
    // 高8位
    outb(0x3d4, 0xC);
    outb(0x3d5, (uint8_t)(pos>>8 & 0xff)); 
    
    update_cursor_pos(console);
    curr_console_idx = tty_idx;
}